<?php
/**
 * WorkOrderRepo.php
 *
 * @copyright   2023 thingsworld.com.cn - All Rights Reserved
 * @link        http://thingsworld.com.cn/
 * @author      stiffer.chen <chenl@thingsworld.com.cn>
 * @created     2023-02-18 16:05:48
 * @modified    2023-02-18 16:05:48
 *
 */

/**
 * 转派
 *  创建人发申请给主管，主管去转派
 */

namespace App\Repositories;

use App\Library\Logger;
use App\Models\CityModel;
use App\Models\Customer;
use App\Models\Dept\Zone;
use App\Models\Event;
use App\Models\Product;
use App\Models\Project\History;
use App\Models\User;
use App\Models\UserModel;
use App\Models\WorkOrder;
use App\Models\WorkOrder\Audit;
use App\Services\DeptService;
use App\Services\ExportXlsxService;
use App\Services\SmsService;
use App\Services\WsService;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use mysql_xdevapi\Exception;
use phpDocumentor\Reflection\Types\Self_;
use function PHPUnit\Framework\isEmpty;

class WorkOrderRepo
{
    const TYPE_DEBUG = '调试工单';
    const TYPE_INSTALL = '安装工单';
    const TYPE_MAINTENANCE = '维保工单';
    const TYPE_TRAIN = '培训工单';

    const STATUS_CREATED = '已创建';
    const STATUS_ASSIGNED = '已指派';
    const STATUS_ACCEPT = '已接受';
    const STATUS_BEGIN = '已开始';
    const STATUS_COMPLETE = '已完成';
    const STATUS_CLOSED = '已关闭';
    const STATUS_EXCEPTION = '异常工单';

    const TYPES_IN_FREE = '保内免费';
    const TYPES_IN_CHARGE = '保内收费';
    const TYPES_OUT_FREE = '保外免费';
    const TYPES_OUT_CHARGE = '保外收费';

    const CONTENT_CHECK = '检测';
    const CONTENT_INSPECT = '检查';
    const CONTENT_INSTALL = '安装';
    const CONTENT_REPAIR = '维修';
    const CONTENT_DEBUG = '调试';

    const LEVEL_IMPORTANT = '重要';
    const LEVEL_URGENCY = '紧急';
    const LEVEL_NORMAL = '普通';
    const LEVEL_LOW = '较低';

    const PROJECT_NAME_INSTALL = '安装服务';
    const PROJECT_NAME_BUILD = '安装施工';
    const PROJECT_NAME_GUIDE = '施工技术指导';
    const PROJECT_NAME_DEBUG = '调试服务';
    const PROJECT_NAME_PUNCHING = '打孔';

    const PROJECT_UNIT_ITEM = '项';
    const PROJECT_UNIT_COUNT = '个';

    const RECEIPT_REASON_QUALITY = '设备质量';
    const RECEIPT_REASON_OPERATION = '客户操作';
    const RECEIPT_REASON_PROTECT = '缺乏保养';
    const RECEIPT_REASON_OLD = '正常旧损';

    const RECEIPT_TYPE_CHANGE = '更换配件';
    const RECEIPT_TYPE_REPAIR = '维修配件';
    const RECEIPT_TYPE_MODIFY = '调整程序';

    const ABOUT_ME_DEPARTMENT = 'department';
    const ABOUT_ME_ASSIST = 'assist';
    const ABOUT_ME_MASTER = 'master';
    const ABOUT_ME_CREATE = 'create';

    const STATUSES_UN_COMPLETE = [
        self::STATUS_CREATED,
        self::STATUS_ASSIGNED,
        self::STATUS_ACCEPT,
        self::STATUS_BEGIN
    ];

    const STATUSES_UN_ASSIGN = [
        self::STATUS_CREATED
    ];

    const STATUSES_ASSIGNED = [
        self::STATUS_ASSIGNED
    ];

    const STATUSES_ACCEPT = [
        self::STATUS_ACCEPT
    ];

    const STATUSES_PROCESS = [
        self::STATUS_BEGIN
    ];

    const STATUSES_COMPLETE = [
        self::STATUS_COMPLETE
    ];

    const STATUSES_CLOSES = [
        self::STATUS_CLOSED
    ];

    /**
     * 工单指派
     * 触发点: 派单时
     * 接收对象: 工单负责人 + 协办人 + 工单联系人
     */
    const NOTIFY_SEND = [
        'title' => '新工单通知',
        'content' => "您有一个新的工单，请尽快处理"
    ];

    const NOTIFY_APPLY = [
        'title' => '工单转派申请',
        'content' => "您有一个新的工单转派申请，请尽快处理"
    ];

    /**
     * 工单指派 sms通知
     * 触发点: 派单时
     * 接收对象: 客户
     */
    const SMS_SEND = [
        'sms_code' => 'SMS_463610395',
        'variables' => []
    ];

    const SMS_NOTIFY_NEW = [
        'sms_code' => 'SMS_463685462',
        'variables' => [
            'wo_number'
        ]
    ];

    /**
     * 工单相应
     * 触发点: 工单被接受时
     * 接收对象: 创建人 + 区域主管 + 工单联系人
     */
    const NOTIFY_ACCEPT = [
        'title' => '工单响应通知',
        'content' => "工单: %s 负责人已接受"
    ];

    /**
     * 工单响应 sms通知
     * 触发点: 工单被接受时
     * 接收对象: 客户
     * 参数:
     *      plan_time: 计划时间
     *      user: 工程师姓名
     *      phone: 工程师电话
     */
    const SMS_ACCEPT = [
        'sms_code' => 'SMS_276442139',
        'variables' => [
            'plan_time',
            'user',
            'phone'
        ]
    ];

    /**
     * 工单完成
     * 触发点: 完成工单时
     * 接收对象: 创建人 + 区域主管 + 区域经理
     */
    const NOTIFY_COMPLETE = [
        'title' => '工单完成通知',
        'content' => "工单: %s 负责人已完成，请审核"
    ];

    /**
     * 工单完成 sms通知
     * 触发点: 完成工单时
     * 接收对象: 客户
     * 参数:
     *      sms_user: 工程师姓名
     *      sms_umobile: 工程师电话
     */
    const SMS_COMPLETE = [
        'sms_code' => 'SMS_463630416',
        'variables' => [
            'sms_user',
            'sms_umobile'
        ]
    ];

    /**
     * 工单超时
     * 触发点: 完成超时时
     * 接收对象: 创建人 + 区域主管 + 区域经理
     * 3天 -> 售后经理
     * 7天 -> 售后总监
     * 15天 -> 总经理
     */
    const NOTIFY_TIMEOUT = [
        'title' => '工单超时通知',
        'content' => "工单: %s 已超时，请关注"
    ];

    // 主管审核
    const AUDIT_CHARGE = 'charge';
    const CALLBACK_CHARGE = 'charge';

    // 经理审核
    const AUDIT_MANAGER = 'manager';
    const CALLBACK_MANAGER = 'manager';

    // 回访满意度 code
    const CALLBACK_SATISFACTION = [
        'low',
        'mid',
        'high'
    ];

    const CALLBACK_CONTENT_CHECK = [
        'fit',
        'deviation'
    ];

    public static function reassignedApply($id)
    {
        $workOrder = WorkOrder::find($id);
        if (empty($workOrder)) {
            throw new \Exception('工单不存在');
        }
        if (!$workOrder->can_reassigned_apply) {
            throw new \Exception("转派申请发起失败, 权限或工单状态异常");
        }
        // 工单省份所属部门的部门主管(区域主管)
        $provinceId = $workOrder->province_id;
        $zone = Zone::byProvinceId($provinceId)->first();
        if (empty($zone)) {
            throw new \Exception("转派申请发起失败, 当前工单区域信息异常");
        }
        $dept = $zone->dept;
        $deptId = $dept->id;
        $leaders = (new DeptService())->getLeadersById($deptId);
        if (!$leaders->count()) {
            throw new \Exception("工单所属区域无区域主管");
        }
        $notifyUserIds = [];
        foreach ($leaders as $leader) {
            $notifyUserIds[] = $leader->id;
        }
        NotificationRepo::multiStore($notifyUserIds, $workOrder->id, NotificationRepo::TYPE_WORK_ORDER, self::NOTIFY_APPLY['title'], sprintf(self::NOTIFY_APPLY['content'], $workOrder->wo_number));
    }

    // 转派
    public static function reassigned($id)
    {
        $workOrder = WorkOrder::find($id);
        if (empty($workOrder)) {
            throw new \Exception('工单不存在');
        }
        $canReassigned = $workOrder->can_reassigned;
        if (!$canReassigned) {
            throw new \Exception('该工单不允许转派');
        }
        $userId = intval(request('user_id', 0));
        if (!$userId) {
            throw new \Exception('请选择要转派的工单负责人');
        }
        $reassignedUser = UserModel::find($userId);

        $users = $workOrder->users;
        $masterUserId = 0;
        foreach ($users as $user) {
            if ($user->is_master == 1) {
                $masterUserId = $user->user_id;
                break;
            }
        }
        $masterUser = UserModel::find($masterUserId);
        if ($masterUserId == $userId) {
            throw new \Exception('不能将工单转派给现有负责人');
        }

        $woNumber = $workOrder->wo_number;

        $workOrder->status = self::STATUS_ASSIGNED;
        $workOrder->saveorFail();

        WorkOrder\User::query()
            ->where('work_order_id', $workOrder->id)
            ->where('is_master', 1)
            ->update(['user_id' => $userId]);

        $history = new WorkOrder\StatusHistory(['status' => self::STATUS_ASSIGNED]);
        $workOrder->histories()->save($history);

        // 通知
        NotificationRepo::multiStore([$userId], $workOrder->id, NotificationRepo::TYPE_WORK_ORDER, self::NOTIFY_SEND['title'], sprintf(self::NOTIFY_SEND['content'], $woNumber));
        // log
        $action = $masterUser->realname . "把工单 #{$woNumber} 转派给了 " . $reassignedUser->realname;
        $bodyData = [
            'creator' => $masterUser->realname,
            'action' => $action
        ];
        OperationLogRepo::createLog(OperationLogRepo::BUSINESS_TYPE_WO, $workOrder->id, json_encode($bodyData));
    }

    // 状态流程
    public static function flow()
    {
        return [
            self::STATUS_CREATED => self::STATUS_ASSIGNED,
            self::STATUS_ASSIGNED => self::STATUS_ACCEPT,
            self::STATUS_ACCEPT => self::STATUS_BEGIN,
            self::STATUS_BEGIN => self::STATUS_COMPLETE
        ];
    }

    public static function types()
    {
        return [
            self::TYPE_DEBUG,
            self::TYPE_INSTALL,
            self::TYPE_MAINTENANCE,
            self::TYPE_TRAIN
        ];
    }

    public static function params()
    {
        return [
            'wo_types' => [
                self::TYPE_MAINTENANCE,
                self::TYPE_DEBUG,
                self::TYPE_INSTALL,
                self::TYPE_TRAIN
            ],
            'levels' => [
                self::LEVEL_IMPORTANT,
                self::LEVEL_URGENCY,
                self::LEVEL_NORMAL,
                self::LEVEL_LOW
            ],
            'status' => [
                self::STATUS_CREATED,
                self::STATUS_ASSIGNED,
                self::STATUS_ACCEPT,
                self::STATUS_BEGIN,
                self::STATUS_COMPLETE,
                self::STATUS_CLOSED,
                self::STATUS_EXCEPTION
            ],
            'types' => [
                self::TYPES_IN_FREE,
                self::TYPES_IN_CHARGE,
                self::TYPES_OUT_FREE,
                self::TYPES_OUT_CHARGE
            ],
            'contents' => [
                self::CONTENT_CHECK,
                self::CONTENT_INSPECT,
                self::CONTENT_INSTALL,
                self::CONTENT_REPAIR,
                self::CONTENT_DEBUG
            ],
            'project_names' => [
                self::PROJECT_NAME_INSTALL,
                self::PROJECT_NAME_BUILD,
                self::PROJECT_NAME_GUIDE,
                self::PROJECT_NAME_DEBUG,
                self::PROJECT_NAME_PUNCHING
            ],
            'project_units' => [
                self::PROJECT_UNIT_ITEM,
                self::PROJECT_UNIT_COUNT
            ],
            'receipt_reason' => [
                self::RECEIPT_REASON_QUALITY,
                self::RECEIPT_REASON_OPERATION,
                self::RECEIPT_REASON_PROTECT,
                self::RECEIPT_REASON_OLD
            ],
            'receipt_type' => [
                self::RECEIPT_TYPE_CHANGE,
                self::RECEIPT_TYPE_REPAIR,
                self::RECEIPT_TYPE_MODIFY
            ],
            'about_me' => [
                ['code' => self::ABOUT_ME_DEPARTMENT, 'text' => '我部门'],
                ['code' => self::ABOUT_ME_ASSIST, 'text' => '我协助'],
                ['code' => self::ABOUT_ME_MASTER, 'text' => '我负责'],
                ['code' => self::ABOUT_ME_CREATE, 'text' => '我创建']
            ]
        ];
    }

    /**
     * @param $userId
     * @return int
     * const STATUS_CREATED = '已创建';
     * const STATUS_ASSIGNED = '已指派';
     * const STATUS_ACCEPT = '已接受';
     */
    public static function statusTimeoutCounts($userId)
    {
        return self::makeBuilder()
            ->where('plan_at', '<=', Carbon::now()->addDays(-3)->toDateString() . ' 00:00:00')
            ->whereIn('status', [self::STATUS_CREATED, self::STATUS_ASSIGNED, self::STATUS_ACCEPT])
            ->count();
    }

    /**
     * 已创建
     *      已生成 且 未指派
     * 已指派
     *      已指派 且 未接单
     * 已接受
     *      已接单 且 未开始处理
     * 已开始
     *      已开始处理 且 未完成 或 已开始 且 未关闭
     * 已完成
     *      已完成
     *      已完成后再关闭
     * 未完成
     *      ≠ 已完成 且 ≠ 已关闭
     * 服务中
     *      已开始 且 未完成 或 已开始 且 未关闭
     * 待处理
     *      已接单 且 未开始处理
     * 已关闭
     *      状态为已关闭
     * 异常
     *      状态: 已创建、已分配、已接受 且 计划时间小于当前时间
     * @return array
     */
    public static function statusCounts($userId = 0)
    {
        $builder = self::makeBuilder(
            [
                'status',
                DB::raw("count('status') as count")
            ],
            $userId
        );
        $builder->groupBy('status');
        $results = $builder->get()->keyBy('status')->toArray();

        $params = self::params();

        // 已创建
        $createdCount = 0;
        // 已指派
        $assignCount = 0;
        // 已接受
        $acceptCount = 0;
        // 已开始
        $beginCount = 0;
        // 已完成
        $completeCount = 0;
        // 已关闭
        $closedCount = 0;
        // 异常
        $exceptionCount = self::makeBuilder()
            ->whereIn('status', [self::STATUS_CREATED, self::STATUS_ASSIGNED, self::STATUS_ACCEPT])
            ->where('plan_at', '<=', Carbon::now()->addDays(-3)->toDateString() . ' 00:00:00')
            ->count();

        // 未完成
        $unCompleteCount = 0;
        // 服务中
        $serviceCount = 0;
        // 待处理
        $unProcessCount = 0;
        // 总服务
        $totalCount = 0;

        foreach ($results as $itemStatus => $result) {
            $itemCount = $result['count'];
            if ($itemStatus == self::STATUS_CREATED) {
                $createdCount = $itemCount;
            }
            if ($itemStatus == self::STATUS_ASSIGNED) {
                $assignCount = $itemCount;
            }
            if ($itemStatus == self::STATUS_ACCEPT) {
                $acceptCount = $itemCount;
            }
            if ($itemStatus == self::STATUS_BEGIN) {
                $beginCount = $itemCount;
            }
            if ($itemStatus == self::STATUS_COMPLETE) {
                $completeCount = $itemCount;
            }
            if ($itemStatus == self::STATUS_CLOSED) {
                $closedCount = $itemCount;
            }
            if ($itemStatus != self::STATUS_COMPLETE && $itemStatus != self::STATUS_CLOSED) {
                $unCompleteCount += $itemCount;
            }
            // 已开始 且 未完成 或 已开始 且 未关闭
            if ($itemStatus == self::STATUS_BEGIN) {
                $serviceCount = $itemCount;
            }
            if ($itemStatus == self::STATUS_ACCEPT) {
                $unProcessCount = $itemCount;
            }
            $totalCount += $itemCount;
        }
        return [
            self::STATUS_CREATED => $createdCount,
            self::STATUS_ASSIGNED => $assignCount,
            self::STATUS_ACCEPT => $acceptCount,
            self::STATUS_BEGIN => $beginCount,
            self::STATUS_COMPLETE => $completeCount,
            self::STATUS_CLOSED => $closedCount,
            self::STATUS_EXCEPTION => $exceptionCount,
            "未完成" => $unCompleteCount,
            "服务中" => $serviceCount,
            "待处理" => $unProcessCount,
            "总服务" => $totalCount
        ];
    }

    public static function fetchByEventId($eventId)
    {
        $workOrder = WorkOrder::byEventId($eventId)->first();
        return self::fetch($workOrder->id ?? 0);
    }

    /**
     * 工单列表
     *      超级管理员: 全部
     *      非超级管理员:
     *          非工程师: 自己创建的 + 本区域的
     *          工程师: 分配给自己的
     *      仅区域主管和区域经理 过滤 审核 和 回访
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public static function getList()
    {
        $builder = self::makeBuilder();
        $builder->with(['products.product', 'histories', 'users.user', 'create_user', 'customer', 'images', 'receipt', 'question_feedback']);
        $builder->orderByDesc('work_order.update_time');
        $builder->where('work_order.id', '<>', null);
        $builder->distinct();

        $perPage = (int)request('limit');
        if (!$perPage) {
            $perPage = 10;
        }
        if ($perPage < 1) {
            $perPage = 1;
        }

        // 是否导出
        $export = intval(request()->get('export') ?? 0);

        if ($export) {
            $page = intval(request()->get('page') ?? 1);
            // $builder->offset(($page - 1) * $perPage);
            // $builder->limit($perPage);
            return self::export($builder);
        }
        $recordTotal = $builder->get()->count();
        $result = $builder->paginate($perPage);
        $result->record_total = $recordTotal;
        $result->transform(function ($item) {
            $qf = $item->question_feedback;
            unset($item->question_feedback);
            $item->callback_show = self::callback_show($item->id);
            $item->audit_show = self::audit_show($item->id);
            $item->qf = empty($qf) ? false : true;
            $item->qf_id = $qf->id ?? 0;
            return $item;
        });
        return $result;
    }

    private static function makeBuilder($select = [], $userId = 0)
    {
        if (!$userId) {
            $currentUserId = registry('user_id');
            $currentUser = registry('user');
        } else {
            $currentUserId = $userId;
            $currentUser = UserModel::find($currentUserId);
        }
        $isSuperAdmin = $currentUser->is_super_admin ?? false;
        $isCharge = is_charge($currentUserId);
        $isManager = is_manager($currentUserId);

        $status = trim(request()->get('status') ?? '');
        $type = trim(request()->get('type') ?? '');
        $dateType = trim(request()->get('date_type') ?? '');
        $aboutMe = trim(request()->get('about_me') ?? '');

        $workStatus = [];
        if ($status == 'un_complete') {
            $workStatus = self::STATUSES_UN_COMPLETE;
        } elseif ($status == 'un_assign') {
            $workStatus = self::STATUSES_UN_ASSIGN;
        } elseif ($status == 'assigned') {
            $workStatus = self::STATUSES_ASSIGNED;
        } elseif ($status == 'accept') {
            $workStatus = self::STATUSES_ACCEPT;
        } elseif ($status == 'process') {
            $workStatus = self::STATUSES_PROCESS;
        } elseif ($status == 'complete') {
            $workStatus = self::STATUSES_COMPLETE;
        } elseif ($status == 'closed') {
            $workStatus = self::STATUSES_CLOSES;
        }

        $builder = WorkOrder::query();
        if (empty($select)) {
            $builder->select(['work_order.*']);
        } else {
            $builder->select($select);
        }
        if ($workStatus) {
            $builder->whereIn('work_order.status', $workStatus);
        }

        if ($status == 'exception') {
            $builder->where(DB::raw('left(' . DB::connection()->getTablePrefix() . 'work_order.plan_at, 10)'), '<=', Carbon::now()->toDateString())
                ->whereIn('work_order.status', [self::STATUS_CREATED, self::STATUS_ASSIGNED, self::STATUS_ACCEPT]);
        }

        if ($type) {
            $builder->where('work_order.type', $type);
        }

        $dateStart = $dateEnd = '';
        if ($dateType == 'week' || $dateType == 'month') {
            if ($dateType == 'week') {
                $dateStart = Carbon::now()->startOfWeek()->toDateString() . ' 00:00:00';
                $dateEnd = Carbon::now()->endOfWeek()->toDateString() . ' 23:59:59';
            }
            if ($dateType == 'month') {
                $dateStart = Carbon::now()->startOfMonth()->toDateString() . ' 00:00:00';
                $dateEnd = Carbon::now()->endOfMonth()->toDateString() . '23:59:59';
            }
        } else {
            $queryDateStart = trim(request('date_start', ''));
            $queryDateEnd = trim(request('date_end', ''));

            if ($queryDateStart) {
                try {
                    $queryDateStart = Carbon::parse($queryDateStart)->toDateTimeString();
                } catch (\Exception $e) {
                    $queryDateStart = '';
                }
            }
            if ($queryDateEnd) {
                try {
                    $queryDateEnd = Carbon::parse($queryDateEnd)->toDateTimeString();
                } catch (\Exception $e) {
                    $queryDateEnd = '';
                }
            }
            if ($queryDateStart) {
                $dateStart = $queryDateStart;
            }
            if ($queryDateEnd) {
                $dateEnd = $queryDateEnd;
            }
        }
        if ($dateStart) {
            $dateStart = Carbon::parse($dateStart)->timestamp;
            $builder->where('work_order.create_time', '>=', $dateStart);
        }
        if ($dateEnd) {
            $dateEnd = Carbon::parse($dateEnd)->timestamp;
            $builder->where('work_order.create_time', '<=', $dateEnd);
        }

        if ($aboutMe) {
            if ($aboutMe == 'department') {
                $user = registry('user');
                $deptId = $user->dept_id ?? 0;
                $userIds = UserModel::query()
                    ->where('dept_id', $deptId)
                    ->select(['id'])
                    ->get()
                    ->pluck('id')
                    ->toArray();
                $userIds = array_unique(array_merge([registry('user_id')], $userIds));
                $builder->whereIn('create_user_id', $userIds);
            }
            if ($aboutMe == 'assist' || $aboutMe == 'master') {
                $builder->join('work_order_user', 'work_order_user.work_order_id', '=', 'work_order.id')
                    ->where('work_order_user.user_id', registry('user_id'));
                if ($aboutMe == 'assist') {
                    $builder->where('work_order_user.is_master', '=', 0);
                }
                if ($aboutMe == 'master') {
                    $builder->where('work_order_user.is_master', '=', 1);
                }
            }
            if ($aboutMe == 'create') {
                $builder->where('work_order.create_user_id', registry('user_id'));
            }
        }

        $dateStart = $dateEnd = 0;
        $dateParam =  \request()->get('date');
        if (!is_null($dateParam) && !is_string($dateParam)) {
            throw new \Exception('日期参数异常');
        }
        $date = trim(strval($dateParam) ?? '');
        if ($date == 'today') {
            $dateNow = Carbon::now()->toDateString();
            $start = $dateNow . ' 00:00:00';
            $end = $dateNow . ' 23:59:59';
            $dateStart = strtotime($start);
            $dateEnd = strtotime($end);
        }

        if ($date == 'week') {
            $dateFirst = Carbon::now()->startOfWeek()->toDateString();
            $dateNow = Carbon::now()->toDateString();
            $start = $dateFirst . ' 00:00:00';
            $end = $dateNow . ' 23:59:59';
            $dateStart = strtotime($start);
            $dateEnd = strtotime($end);
        }

        if ($date == 'month') {
            $dateFirst = Carbon::now()->startOfMonth()->toDateString();
            $dateNow = Carbon::now()->toDateString();
            $start = $dateFirst . ' 00:00:00';
            $end = $dateNow . ' 23:59:59';
            $dateStart = strtotime($start);
            $dateEnd = strtotime($end);
        }

        if ($date == 'year') {
            $dateFirst = Carbon::now()->startOfYear()->toDateString();
            $dateNow = Carbon::now()->toDateString();
            $start = $dateFirst . ' 00:00:00';
            $end = $dateNow . ' 23:59:59';
            $dateStart = strtotime($start);
            $dateEnd = strtotime($end);
        }

        if ($dateStart) {
            $builder->where('work_order.create_time', '>=', $dateStart);
        }
        if ($dateEnd) {
            $builder->where('work_order.create_time', '<=', $dateEnd);
        }

        $number = trim(request()->get('number') ?? '');
        if ($number) {
            $builder->where('work_order.wo_number', 'like', "%{$number}%");
        }

        $currentUserDeptId = $currentUser->dept_id ?? 0;
        $deptService = new DeptService();
        // 所有能看的省份
        $allProvinceIds = $deptService->getAllProvinceIds($currentUserDeptId);
        if ($currentUserId != 1) {
            if (is_engineer($currentUserId)) {
                // 工程师: 分配给自己的
                $builder->join('work_order_user as wou', 'wou.work_order_id', '=', 'work_order.id')
                    ->where('wou.user_id', $currentUserId);
            } else {
                // 非工程师: 自己创建的 + 分配给自己的 + 当前省份的
                $builder->where(function ($query) use ($allProvinceIds, $currentUserId) {
                    $query->whereIn('work_order.province_id', $allProvinceIds)
                        ->orWhere('work_order.create_user_id', $currentUserId)
                        ->orWhere('wou.user_id', $currentUserId);
                });
                $builder->leftJoin('work_order_user as wou', 'wou.work_order_id', '=', 'work_order.id');
            }
        }
        $auditManager = intval(request()->get('audit_manager'));
        if ($auditManager) {
            $builder->join('work_order_audit as woa', 'woa.work_order_id', '=', 'work_order.id')
                ->where('woa.target', 'manager');
        }

        /**
         * 所有人都需要筛选出是否已回访过或审核过数据
         */
        $audited = request()->get('audited');
        $callbacked = request()->get('callbacked');

        if (!is_null($callbacked)) {
            $callbacked = intval($callbacked);
            // 前置条件 二级审核已过
            $builder->whereIn('work_order.id', function ($query) {
                $query->select(['work_order_id'])
                    ->from('work_order_audit')
                    ->where('target', '=', 'manager');
            });
            $builder->leftJoin('work_order_callback as woc', 'woc.work_order_id', '=', 'work_order.id');
            if ($callbacked) {
                $builder->where('woc.id', '<>', null);
            } else {
                $builder->where('woc.id', null);
            }
        }

        if (!is_null($audited)) {
            $audited = intval($audited);
            if ($isCharge) {
                /**
                 * 主管已审核: target = charge
                 * 主管未审核: target <> charge && target <> manager
                 */
                $builder->leftJoin('work_order_audit as woa', 'woa.work_order_id', '=', 'work_order.id');
                if ($audited) {
                    $builder->where('woa.target', '<>', null);
                } else {
                    $builder->where('woa.target', '=', null);
                }
            } elseif ($isManager) {
                /**
                 * 区域经理
                 * 前置条件: 一级审核已过
                 * target = manager
                 * 已审核: woa.target <> null
                 * 未审核: woa.target = null
                 */
                $builder->join('work_order_audit as woa', 'woa.work_order_id', '=', 'work_order.id');
                $builder->where('woa.target', '=', 'charge');
                if ($audited) {
                    $builder->whereIn('work_order.id', function ($query) {
                        $query->select(['work_order_id'])
                            ->from('work_order_audit')
                            ->where('target', '=', 'manager');
                    });
                } else {
                    $builder->whereNotIn('work_order.id', function ($query) {
                        $query->select(['work_order_id'])
                            ->from('work_order_audit')
                            ->where('target', '=', 'manager');
                    });
                }
            }
        }

        $satisfaction = intval(request('satisfaction') ?? '');
        if (in_array($satisfaction, [1, 2, 3, 4, 5])) {
            $builder->join('work_order_receipt as wor', 'wor.work_order_id', '=', 'work_order.id');
            if ($satisfaction == 1) {
                $builder->where('wor.satisfaction', '<', 2);
            } elseif ($satisfaction == 2) {
                $builder->where('wor.satisfaction', '=', 2);
            } elseif ($satisfaction == 3) {
                $builder->where('wor.satisfaction', '=', 3);
            } elseif ($satisfaction == 4) {
                $builder->where('wor.satisfaction', '=', 4);
            } else {
                $builder->where('wor.satisfaction', '>', 4);
            }
        }

        // 负责人ID
        $searchUserId = intval(request('user_id', 0));
        // 部门ID
        $searchDeptId = intval(request('dept_id', 0));
        if ($searchUserId || $searchDeptId) {
            $builder->join('work_order_user', 'work_order_user.work_order_id', '=', 'work_order.id');
            if ($searchUserId) {
                $builder->where('work_order_user.user_id', '=', $searchUserId)
                    ->where('work_order_user.is_master', '=', 1);
            }
            if ($searchDeptId) {
                $builder->join('user', 'user.id', '=', 'work_order_user.user_id')
                    ->where('user.dept_id', '=', $searchDeptId);
            }
        }

        return $builder;
    }

    private static function export($builder)
    {
        $titles = [
            ['title' => '工单信息', 'count' => 15],
            ['title' => '完成信息', 'count' => 13],
            ['title' => '系统信息', 'count' => 20],
        ];
        $columnHeads = [
            '工单编号',
            '工单类型',
            '客户',
            '联系人',
            '电话',
            '客户地址',
            '产品名称',
            '产品编号',
            '质保开始时间',
            '质保结束时间',
            '质保状态',
            '优先级',
            '服务类型',
            '服务内容',
            '计划时间',
            '备件名称',
            '备件编号',
            '备件类别',
            '备件规格',
            '备件数量',
            '备件价格',
            '服务项目',
            '服务类别',
            '服务项目数量',
            '服务项目金额',
            '工单总价',
            '折扣费用',
            '工单合计',
            '工单状态',
            '创建人',
            '派单人',
            '负责人',
            '审核人',
            '服务网点',
            '协同人',
            '创建时间',
            '派单时间',
            '接受时间',
            '开始时间',
            '转派时间',
            '完成时间',
            '工单用时',
            '审核状态',
            '回访时间',
        ];

        $fileName = 'work_order';

        $mainData = self::getExcelMainData($builder->get());

        $service = new ExportXlsxService($titles, $columnHeads, $mainData);
        $filePath = $service->export($fileName);
        return $filePath;
    }

    private static function getExcelMainData($workOrders)
    {
        $result = [];
        if (!$workOrders->count()) {
            return $result;
        }
        foreach ($workOrders as $item) {
            $customerName = $item->customer->name ?? '';
            $address = $item->address;
            $itemProducts = $item->products;
            $resultProductNames = $resultProductModels = [];
            $warrantyStart = $warrantyStartEnd = $warrantyStatus = '';
            foreach ($itemProducts as $itemProduct) {
                $itemProductDetail = $itemProduct->product;
                $resultProductNames[] = $itemProductDetail->name ?? '';
                $resultProductModels[] = $itemProductDetail->model ?? '';
                $warrantyStart = $itemProductDetail->warranty_start ?? '';
                $warrantyStartEnd = $itemProductDetail->warranty_end ?? '';
                $warrantyStatus = $itemProductDetail->warranty_status_title ?? '';
            }

            $receipt = $item->receipt;

            // 备件信息
            $boms = $receipt->boms ?? [];
            $bomNames = $bomModels = $bomSpecifications = $bomCounts = [];
            // 备件价格: 暂无
            $bomPrice = '';
            // 备件类别: 暂无
            $bomType = '';
            if ($boms != [] && $boms->count()) {
                foreach ($boms as $itemBom) {
                    $itemBomDetail = $itemBom->bom;
                    $bomNames[] = $itemBomDetail->name ?? '';
                    $bomModels[] = $itemBomDetail->model ?? '';
                    $bomSpecifications[] = $itemBomDetail->specification ?? '';
                    $bomCounts[] = $itemBom->count ?? 0;
                }
            }
            $bomName = implode(',', $bomNames);
            $bomModel = implode(',', $bomModels);
            $bomType = $bomType;
            $bomCount = implode(',', $bomCounts);
            $bomPrice = $bomPrice;
            $bomSpecification = implode(',', $bomSpecifications);

            // 服务相关
            $services = $receipt->services ?? [];
            // 服务项目
            $serviceNames = [];
            // 服务类别: 暂无
            $serviceTypes = [];
            // 服务数量
            $serviceCounts = [];
            // 项目金额
            $serviceFees = [];

            // 工单总价
            $woTotal = 0;
            // 折扣费用: 暂无
            $woDiscount = 0;

            if ($services != [] && $services->count()) {
                foreach ($services as $itemService) {
                    $serviceNames[] = $itemService->name;
                    $serviceCounts[] = $itemService->count;
                    $serviceFees[] = round($itemService->fee, 2);
                    $woTotal += $itemService->fee;
                }
            }

            $serviceName = implode(',', $serviceNames);
            $serviceType = implode(',', $serviceTypes);
            $serviceCount = implode(',', $serviceCounts);
            $serviceFee = implode(',', $serviceFees);

            // 派单人: 暂无，需要在工单历史表中添加操作人ID
            $allotName = '';
            // 负责人
            $masterName = '';
            // 协作人
            $normalName = '';
            // 审核人
            $auditName = '';

            $users = $item->users->groupBy('is_master')->toArray();
            $masterUser = $users[1][0]['user'] ?? [];
            $masterName = $masterUser['realname'] ?? '';
            $normals = $users[0] ?? [];
            $normalNames = [];
            foreach ($normals as $itemNormal) {
                $normalNames[] = $itemNormal['user']['realname'] ?? '';
            }
            $normalName = implode(',', $normalNames);

            $logs = $item->logs ?? [];
            foreach ($logs as $log) {
                $itemAction = $log['action'];
                if (strrpos($itemAction, '指派') !== false) {
                    $allotName = $log['creator'];
                }
            }

            // 审核状态
            $auditStatus = '未审核';
            $audit = $item->audit;
            if ($audit->count()) {
                $auditStatus = '一级';
                $auditNames = [];
                foreach ($audit as $itemAudit) {
                    if ($itemAudit->target == 'manager') {
                        $auditStatus = '二级';
                    }
                    $auditNames[] = $itemAudit->user->realname ?? '';
                }
                $auditName = implode(',', $auditNames);

            }

            $dept = $item->dept_zone->dept ?? null;
            $serviceNet = '';
            if (!empty($dept)) {
                $serviceNet = $dept->name ?? '';
            }

            $histories = $item->histories->toArray();
            // 派单时间
            $allotTime = '';
            // 接受时间
            $acceptTime = '';
            // 开始时间
            $startTime = '';
            // 转派时间: 暂无
            $changeTime = '';
            // 完成时间
            $completeTime = '';

            // 接单用时: 派单 -> 接单
            $allotHours = '';
            // 工单用时: 接单 -> 完成
            $woHours = '';
            // 工作用时: 开始 -> 完成
            $workHours = '';
            // 响应用时: 创建 -> 开始
            $responseHours = '';
            // 完工用时: 待定
            $completeHours = '';

            $allotTimestamp = 0; // 派单
            $acceptTimestamp = 0; // 接单
            $beginTimestamp = 0; // 开始
            $completeTimestamp = 0; // 完成
            $createTimestamp = 0; // 创建

            foreach ($histories as $history) {
                $historyStatus = $history['status'];
                $historyCreateTime = $history['create_time_format'];
                if ($historyStatus == WorkOrderRepo::STATUS_ASSIGNED) {
                    $allotTimestamp = $history['create_time'];
                    $allotTime = $historyCreateTime;
                }
                if ($historyStatus == WorkOrderRepo::STATUS_ACCEPT) {
                    $acceptTimestamp = $history['create_time_format'];
                    $acceptTime = $historyCreateTime;
                }
                if ($historyStatus == WorkOrderRepo::STATUS_BEGIN) {
                    $beginTimestamp = $history['create_time_format'];
                    $startTime = $historyCreateTime;
                }
                if ($historyStatus == WorkOrderRepo::STATUS_COMPLETE) {
                    $completeTimestamp = $history['create_time_format'];
                    $completeTime = $historyCreateTime;
                }
                if ($historyStatus == WorkOrderRepo::STATUS_CREATED) {
                    $createTimestamp = $historyCreateTime;
                }
            }

            if ($allotTimestamp && $acceptTimestamp) {
                $allotSeconds = Carbon::parse($allotTimestamp)->diffInSeconds(Carbon::parse($acceptTimestamp), true);
                $allotHours = second_2_diff($allotSeconds);
            }

            if ($allotTimestamp && $completeTimestamp) {
                $woMinutes = Carbon::parse($allotTimestamp)->diffInSeconds(Carbon::parse($completeTimestamp), true);
                $woHours = second_2_diff($woMinutes);
            }

            if ($beginTimestamp && $completeTimestamp) {
                $workMinutes = Carbon::parse($beginTimestamp)->diffInSeconds(Carbon::parse($completeTimestamp), true);
                $workHours = second_2_diff($workMinutes);
                $completeHours = $woHours;
            }

            if ($createTimestamp && $beginTimestamp) {
                $responseMinutes = Carbon::parse($createTimestamp)->diffInSeconds(Carbon::parse($beginTimestamp), true);
                $responseHours = second_2_diff($responseMinutes);
            }

            $callbackTime = '';
            $callback = $item->callback;
            if (!empty($callback)) {
                $callbackTime = Carbon::parse($callback->create_time)->toDateTimeString();
            }

            $result[] = [
                ' ' . $item->wo_number,
                ' ' . $item->type,
                ' ' . $customerName,
                ' ' . $item->contact_name,
                ' ' . $item->contact_phone,
                ' ' . $address,
                ' ' . implode(',', $resultProductNames),
                ' ' . implode(',', $resultProductModels),
                ' ' . $warrantyStart,
                ' ' . $warrantyStartEnd,
                ' ' . $warrantyStatus,
                ' ' . $item->level,
                ' ' . $item->service_type,
                ' ' . $item->service_content,
                ' ' . Carbon::parse($item->plan_at)->toDateString(),
                ' ' . $bomName,
                ' ' . $bomModel,
                ' ' . $bomType,
                ' ' . $bomSpecification,
                ' ' . $bomCount,
                ' ' . $bomPrice,
                ' ' . $serviceName,
                ' ' . $serviceType,
                ' ' . $serviceCount,
                ' ' . $serviceFee,
                ' ' . round($woTotal, 2),
                ' ' . round($woDiscount, 2),
                ' ' . round($woTotal, 2),
                ' ' . $item->status,
                ' ' . $item->create_user->realname ?? '',
                ' ' . $allotName,
                ' ' . $masterName,
                ' ' . $auditName,
                ' ' . $serviceNet,
                ' ' . $normalName,
                ' ' . Carbon::parse($item->create_time)->toDateTimeString(),
                ' ' . $allotTime,
                ' ' . $acceptTime,
                ' ' . $startTime,
                ' ' . $changeTime,
                ' ' . $completeTime,
                ' ' . $woHours,
                ' ' . $auditStatus,
                ' ' . $callbackTime
            ];
        }
        return $result;
    }

    /**
     * 事件转工单时，关闭工单
     * 消息target: 创建人 + 区域主管 + 工单负责人
     * @param $eventId
     */
    public static function closeEvent($eventId)
    {
        $event = Event::find($eventId);
        if (empty($event)) {
            return;
        }
        $createdBy = $event->created_by;
        $createdType = $event->created_type;
        $provinceId = $event->province_id;
        // 主管ID
        $leaderIds = (new DeptService())->getLeaderIdsByProvinceId($provinceId);
        $pushUserIds = $leaderIds;
        $eventUsers = $event->users->groupBy('is_master')->toArray();
        // 负责人ID
        $eventMasterUserId = $eventUsers[1][0]['user_id'] ?? 0;
        if ($eventMasterUserId) {
            $pushUserIds = array_merge($pushUserIds, [$eventMasterUserId]);
        }
        // 创建人
        if ($createdType == EventRepo::CREATED_USER) {
            $pushUserIds = array_merge($pushUserIds, [$createdBy]);
        }
        $pushUserIds = array_unique($pushUserIds);
        $eventNumber = $event->event_number;
        NotificationRepo::multiStore($pushUserIds, $event->id, NotificationRepo::TYPE_EVENT, EventRepo::NOTIFY_CONV['title'], sprintf(EventRepo::NOTIFY_CONV['content'], $eventNumber));
        self::wsSend($event->id, "事件已转工单处理, 编号: {$eventNumber}");

        // reason: http://pm.thingsworld.com.cn/index.php?m=bug&f=view&bugID=78
        // EventRepo::sendSms($event->id, EventRepo::SMS_CONV, ['event_id' => $event->event_number, 'customer_telephone' => service_telephone()]);

        $event->status = EventRepo::STATUS_CLOSED;
        $event->saveOrFail();
    }

    public static function closeWorkOrder($id)
    {
        $workOrder = WorkOrder::find($id);
        if (empty($workOrder)) {
            throw new \Exception('工单不存在');
        }
        $status = $workOrder->status;
        if ($status == self::STATUS_CLOSED) {
            throw new \Exception('已关闭工单不能重复关闭');
        }
        $workOrder->status = self::STATUS_CLOSED;
        $workOrder->saveOrFail();
    }

    private static function sendSms($woId, $template, $params = [])
    {
        $workOrder = WorkOrder::find($woId);
        if (empty($workOrder)) {
            return;
        }
        $telephone = $workOrder->contact_phone;
        $smsService = new SmsService($telephone);
        $smsCode = $template['sms_code'];
        try {
            $smsService->sendMessage($smsCode, $params);
        } catch (\Exception $e) {
            $logger = new Logger('sms/work_order');
            $logger->error($e->getMessage());
        }
    }

    public static function store($id)
    {
        $log = new Logger('work_order_repo');
        $data = self::requestData($id);
        $mainData = $data['main'];

        $eventId = $mainData['event_id'] ?? 0;
        $history = $data['history'];
        $images = $data['images'];
        $products = $data['products'];
        $status = $mainData['status'];
        if ($id) {
            $workOrder = WorkOrder::find($data);
        } else {
            $workOrder = new WorkOrder();
        }
        $log->info("save work order");
        $event = Event::find($eventId);
        if (empty($event)) {
            $dbEventId = 0;
        } else {
            $dbEventId = $event->id;
        }
        $mainData['event_id'] = $dbEventId;
        $workOrder->fill($mainData);
        $workOrder->saveOrFail();

        $workOrder->images()->delete();
        $workOrder->products()->delete();
        $workOrder->users()->delete();

        $workOrderImages = [];
        foreach ($images as $image) {
            $workOrderImages[] = new WorkOrder\Image([
                'image' => $image
            ]);
        }
        $log->info("save wo images.");
        $workOrder->images()->saveMany($workOrderImages);
        if ($status == self::STATUS_ASSIGNED) {
            $woHistory = new WorkOrder\StatusHistory(['status' => self::STATUS_CREATED]);
            $workOrder->histories()->save($woHistory);
        }
        $woHistory = new WorkOrder\StatusHistory(['status' => $history['status']]);
        $log->info('save wo history');
        $workOrder->histories()->save($woHistory);

        $woProducts = [];
        foreach ($products as $product) {
            $woProducts[] = new WorkOrder\Product([
                'product_id' => $product
            ]);
        }
        $log->info('save wo products');
        $workOrder->products()->saveMany($woProducts);

        $users = [];
        foreach ($data['users'] as $user) {
            $users[] = new WorkOrder\User([
                'user_id' => $user['user_id'],
                'is_master' => $user['is_master']
            ]);
        }
        $log->info('save wo users');
        $workOrder->users()->saveMany($users);

        // 关闭事件，推送消息
        self::closeEvent($eventId);
        $users = $data['users'];
        $workOrderId = $workOrder->id;
        $workOrderNumber = $workOrder->wo_number;
        if ($users) {
            // 工单负责人 + 协办人
            $userIds = collect($users)->pluck('user_id')->toArray();
            // 联系人: app为内部使用，只能通过手机短信发送，此处跳过
            $pushUserIds = $userIds;

            NotificationRepo::multiStore($pushUserIds, $workOrderId, NotificationRepo::TYPE_WORK_ORDER, self::NOTIFY_SEND['title'], sprintf(self::NOTIFY_SEND['content'], $workOrderNumber));
            self::wsSend($workOrderId, sprintf(self::NOTIFY_SEND['content'], $workOrderNumber));
        }
        // 发送通知，记录 Log
        $creatorName = registry('user')->realname ?? '';
        $masterUsername = $mainData['master_username'] ?? '';

        $action = "提交了工单 # {$workOrderNumber}";
        $bodyData = [
            'creator' => $creatorName,
            'action' => $action
        ];
        $log->info('create action logs.');

        OperationLogRepo::createLog(OperationLogRepo::BUSINESS_TYPE_WO, $workOrderId, json_encode($bodyData));
        if ($users) {
            $action = "把工单 #{$workOrderNumber} 指派给了 {$masterUsername}";
            $bodyData = [
                'creator' => $creatorName,
                'action' => $action
            ];
            OperationLogRepo::createLog(OperationLogRepo::BUSINESS_TYPE_WO, $workOrderId, json_encode($bodyData));
        }

        // self::sendSms($workOrder->id, self::SMS_SEND);

        return self::fetch($workOrder->id);
    }

    /**
     * 当前用户是否是工单的负责人
     * @param $workOrderId
     * @return bool
     */
    private static function isMaster($workOrderId): bool
    {
        $workOrder = WorkOrder::find($workOrderId);
        if (empty($workOrder)) {
            return false;
        }
        $masterUser = WorkOrder\User::query()
            ->where('work_order_id', $workOrderId)
            ->where('is_master', 1)
            ->first();
        $masterUserId = $masterUser->user_id ?? 0;

        return $masterUserId == intval(registry('user_id'));
    }

    public static function fetch($id)
    {
        $workOrder = WorkOrder::query();
        $workOrder->where('id', $id);
        $workOrder->with(['images', 'products.product', 'histories', 'users.user', 'create_user', 'customer', 'receipt.images', 'receipt.services']);
        // $workOrder->whereIn('province_id', [0]);
        $currentUser = registry('user');
        $currentUserId = registry('user_id');
        $currentUserDeptId = $currentUser->dept_id;
        $deptService = new DeptService();
        // 所有能看的省份
        $allProvinceIds = $deptService->getAllProvinceIds($currentUserDeptId);
        if ($currentUserId != 1) {
            // $workOrder->whereIn('work_order.province_id', $allProvinceIds);
        }

        // @todo 只能看当前部门及所有子部门的工单

        if (!$workOrder->count()) {
            return [];
        }
        $workOrder->with(['images', 'products.product', 'histories', 'users.user', 'create_user', 'customer', 'receipt.images', 'receipt.services']);

        $isMaster = self::isMaster($id);

        $result = $workOrder->first();
        $result->is_master = $isMaster;
        $result->callback_show = self::callback_show($id);
        $result->audit_show = self::audit_show($id);
        $questionFeedback = $result->question_feedback;
        $result->qf = empty($questionFeedback) ? false : true;
        $result->qf_id = $questionFeedback->id ?? 0;
        return $result;

//        $workOrder = WorkOrder::with(['images', 'products.product', 'histories', 'users.user', 'create_user', 'customer', 'receipt.images', 'receipt.services'])->find($id);
//        if (empty($workOrder)) {
//            throw new \Exception('工单不存在');
//        }
//        $workOrder->callback_show = self::callback_show($id);
//        $workOrder->audit_show = self::audit_show($id);
//        return $workOrder;
    }

    public static function allot($id)
    {
        $workOrder = WorkOrder::find($id);
        if (empty($workOrder)) {
            throw new \Exception('工单信息不存在');
        }
        if ($workOrder->status != self::STATUS_CREATED) {
            throw new \Exception('仅' . self::STATUS_CREATED . '工单可以进行分配操作');
        }

        $workOrderData = request()->getContent();
        $workOrderArr = json_decode($workOrderData, true);

        $userId = intval($workOrderArr['user_id'] ?? 0);
        $user = UserModel::find($userId);

        $userTelephone = $user->mobile ?? '';

        $masterUser = $user;
        if (empty($user)) {
            throw new \Exception('负责人信息不存在');
        }

        $users = [];
        $users[] = [
            'user_id' => $userId,
            'is_master' => 1
        ];

        self::smsNotifyNew([$userTelephone], self::SMS_NOTIFY_NEW, ['wo_number' => $workOrder->wo_number]);

        $assistUserIds = $workOrderArr['assist_user_ids'] ?? [];
        if ($assistUserIds) {
            foreach ($assistUserIds as $assistUserId) {
                $assistUserId = intval($assistUserId);
                if ($assistUserId <= 0) {
                    continue;
                }
                $users[] = [
                    'user_id' => $assistUserId,
                    'is_master' => 0
                ];
            }
        }

        $workOrderUserIds = collect($users)->pluck('user_id')->toArray();

        $workOrderUsers = [];
        foreach ($users as $user) {
            $workOrderUsers[] = new WorkOrder\User($user);
        }

        $workOrder->status = self::STATUS_ASSIGNED;
        $workOrder->saveOrFail();

        $workOrder->users()->delete();
        $workOrder->users()->saveMany($workOrderUsers);

        $workOrderHistory = new WorkOrder\StatusHistory(['status' => self::STATUS_ASSIGNED]);
        $workOrder->histories()->save($workOrderHistory);
        $creatorName = registry('user')->realname ?? '';
        // 发送通知，记录 Log
        $number = $workOrder->wo_number;
        $masterUsername = $masterUser->realname ?? '';
        $action = "把工单 #{$number} 分配给了 {$masterUsername}";
        $bodyData = [
            'creator' => $creatorName,
            'action' => $action
        ];
        OperationLogRepo::createLog(OperationLogRepo::BUSINESS_TYPE_WO, $workOrder->id, json_encode($bodyData));

        self::wsSend($workOrder->id, "$masterUsername 把工单 #{$number} 分配给了 {$masterUsername}");
        NotificationRepo::multiStore($workOrderUserIds, $id, NotificationRepo::TYPE_WORK_ORDER, self::NOTIFY_SEND['title'], sprintf(self::NOTIFY_SEND['content'], $workOrder->wo_number));
        return self::fetch($id);
    }

    /**
     * 工单通知: 转派和分配时
     *
     * @param $telephones
     * @param $template
     * @param array $params
     */
    private static function smsNotifyNew($telephones, $template, $params = [])
    {
        $log = new Logger('sms/work_order_new');
        if (!$telephones) {
            $log->info("无可用电话号码");
            return;
        }
        foreach ($telephones as $telephone) {
            if (!$telephone) {
                $log->info("电话号码不可用: {$telephone}");
                continue;
            }
            $smsService = new SmsService($telephone);
            try {
                $smsService->sendMessage($template['sms_code'], $params);
            } catch (\Exception $e) {
                $log->error($e->getMessage());
            }
        }
    }

    public static function start($id)
    {
        $workOrder = WorkOrder::find($id);
        if (empty($workOrder)) {
            throw new \Exception('工单不存在');
        }
        if ($workOrder->status != self::STATUS_ACCEPT) {
            throw new \Exception('非已接受的工单不能变更为已开始');
        }

        // @todo 验证工单负责人和当前用户是否是同一个人，不是同一人不能变更工单

        $creatorName = registry('user')->realname ?? '';
        $number = $workOrder->wo_number;
        $action = "开始处理工单 #{$number}";
        $bodyData = [
            'creator' => $creatorName,
            'action' => $action
        ];
        OperationLogRepo::createLog(OperationLogRepo::BUSINESS_TYPE_WO, $workOrder->id, json_encode($bodyData));

        self::wsSend($workOrder->id, "工单 #{$number} 已开始处理");
        $workOrder->status = self::STATUS_BEGIN;
        $workOrder->saveOrFail();

        $woHistory = new WorkOrder\StatusHistory(['status' => self::STATUS_BEGIN]);
        $workOrder->histories()->save($woHistory);

        return self::fetch($id);
    }

    public static function accept($id)
    {
        $workOrder = WorkOrder::find($id);
        if (empty($workOrder)) {
            throw new \Exception('工单不存在');
        }
        if ($workOrder->status != self::STATUS_ASSIGNED) {
            throw new \Exception('非已指派的工单不能变更为已接受');
        }

        $isMaster = self::isMaster($id);
        if (!$isMaster) {
            throw new \Exception('仅工单负责人可以接单');
        }

        // @todo 验证工单负责人和当前用户是否是同一个人，不是同一人不能变更工单

        $planAt = request()->get('plan_at');
        try {
            $planAt = Carbon::parse($planAt)->toDateTimeString();
        } catch (\Exception $e) {
            $planAt = $workOrder->plan_at;
        }

        /**
         * 通知
         * target: 创建人 + 区域主管 + 工单联系人
         * *** 联系人只能通过短信接收 ***
         */
        $provinceId = $workOrder->province_id;
        // 区域主管
        $leaderIds = (new DeptService())->getLeaderIdsByProvinceId($provinceId);
        $pushUserIds = $leaderIds;
        // 创建人
        $creatorId = $workOrder->create_user_id;
        if ($creatorId) {
            $pushUserIds[] = $creatorId;
        }
        NotificationRepo::multiStore($pushUserIds, $workOrder->id, NotificationRepo::TYPE_WORK_ORDER, self::NOTIFY_ACCEPT['title'], sprintf(self::NOTIFY_ACCEPT['content'], $workOrder->wo_number));

        $creatorName = registry('user')->realname ?? '';
        $action = sprintf(self::NOTIFY_ACCEPT['content'], $workOrder->wo_number);
        $bodyData = [
            'creator' => $creatorName,
            'action' => $action
        ];
        OperationLogRepo::createLog(OperationLogRepo::BUSINESS_TYPE_WO, $workOrder->id, json_encode($bodyData));

        self::wsSend($workOrder->id, $action);

        $workOrder->plan_at = $planAt;
        $workOrder->status = self::STATUS_ACCEPT;
        $workOrder->saveOrFail();

        $woHistory = new WorkOrder\StatusHistory(['status' => self::STATUS_ACCEPT]);
        $workOrder->histories()->save($woHistory);

        // 响应工单
        $workOrderParamsData = WorkOrder\User::query()
            ->select(['user.realname', 'user.mobile'])
            ->join('user', 'user.id', 'work_order_user.user_id')
            ->where('work_order_user.is_master', $workOrder->id)
            ->where('work_order_user.work_order_id', $workOrder->id)
            ->first();
        $workOrder = WorkOrder::find($workOrder->id);
        try {
            $planAt = Carbon::parse($workOrder->plan_at)->toDateString();
        } catch (\Exception $e) {
            $planAt = '';
        }

        $smsParams['phone'] = $workOrderParamsData->mobile ?? '';
        $smsParams['user'] = $workOrderParamsData->realname ?? '';
        $smsParams['plan_time'] = $planAt;
        self::sendSms($workOrder->id, self::SMS_ACCEPT, $smsParams);

        return self::fetch($id);
    }

    public static function receipt($id)
    {
        $workOrder = WorkOrder::find($id);
        if (empty($workOrder)) {
            throw new \Exception('工单不存在');
        }
        if ($workOrder->status != self::STATUS_BEGIN) {
            throw new \Exception('非已开始的工单不能进行回执操作');
        }
        $data = self::receiptData();
        $main = $data['main'];
        $images = $data['images'];
        $boms = $data['boms'] ?? [];
        $services = $data['services'];

        $receiptImages = [];
        foreach ($images as $image) {
            $receiptImages[] = new WorkOrder\Receipt\Image([
                'image' => $image
            ]);
        }
        $receiptBoms = [];
        foreach ($boms as $bom) {
            $receiptBoms[] = new WorkOrder\Receipt\Bom([
                'bom_id' => $bom['bom_id'],
                'count' => $bom['count'],
            ]);
        }
        $receiptServices = [];
        foreach ($services as $service) {
            $receiptServices[] = new WorkOrder\Receipt\Service([
                'name' => $service['name'],
                'unit' => $service['unit'],
                'count' => $service['count']
            ]);
        }

        $main['work_order_id'] = $id;

        $receipt = new WorkOrder\Receipt();
        $receipt->fill($main);
        $receipt->saveOrFail();

        $receipt->images()->saveMany($receiptImages);
        $receipt->boms()->saveMany($receiptBoms);
        $receipt->services()->saveMany($receiptServices);

        $workOrder->status = self::STATUS_COMPLETE;
        $workOrder->saveOrFail();

        $woHistory = new WorkOrder\StatusHistory(['status' => self::STATUS_COMPLETE]);
        $workOrder->histories()->save($woHistory);

        /**
         * 通知
         * target: 创建人 + 区域主管 + 区域经理
         */
        $provinceId = $workOrder->province_id;
        $deptService = new DeptService();
        $leaderIds = $deptService->getLeaderIdsByProvinceId($provinceId);
        $managerIds = $deptService->getManagerIdsByProvinceId($provinceId);
        $pushUserIds = array_merge($leaderIds, $managerIds);
        $pushUserIds = array_unique($pushUserIds);
        $pushUserIds = array_merge($pushUserIds, [$workOrder->create_user_id]);
        $pushUserIds = array_unique($pushUserIds);
        NotificationRepo::multiStore($pushUserIds, $workOrder->id, NotificationRepo::TYPE_WORK_ORDER, self::NOTIFY_COMPLETE['title'], sprintf(self::NOTIFY_COMPLETE['content'], $workOrder->wo_number));
        self::wsSend($workOrder->id, sprintf(self::NOTIFY_COMPLETE['content'], $workOrder->wo_number));
        $bodyData = [
            'creator' => registry('user')->realname ?? '',
            'action' => sprintf(self::NOTIFY_COMPLETE['content'], $workOrder->wo_number)
        ];
        OperationLogRepo::createLog(OperationLogRepo::BUSINESS_TYPE_WO, $workOrder->id, json_encode($bodyData));

        // 完成通知
        $workOrderParamsData = WorkOrder\User::query()
            ->select(['user.realname', 'user.mobile'])
            ->join('user', 'user.id', 'work_order_user.user_id')
            ->where('work_order_user.is_master', $id)
            ->where('work_order_user.work_order_id', $id)
            ->first();
        $completeParams['sms_umobile'] = $workOrderParamsData->mobile ?? '';
        $completeParams['sms_user'] = $workOrderParamsData->realname ?? '';
        self::sendSms($id, self::SMS_COMPLETE, $completeParams);

        return self::fetch($id);
    }

    public static function countStatistic()
    {
        $counts = WorkOrder::query()
            ->select([DB::raw('count(*) as count'), 'status'])
            ->groupBy('status')
            ->get()
            ->toArray();

        $unCompleteCount = $unAssignCount = $processCount = $completeCount = 0;

        foreach ($counts as $count) {
            $itemCount = $count['count'];
            $itemStatus = $count['status'];
            if (in_array($itemStatus, self::STATUSES_UN_COMPLETE)) {
                $unCompleteCount += $itemCount;
            }
            if (in_array($itemStatus, self::STATUSES_UN_ASSIGN)) {
                $unAssignCount += $itemCount;
            }
            if (in_array($itemStatus, self::STATUSES_PROCESS)) {
                $processCount += $itemCount;
            }
            if (in_array($itemStatus, self::STATUSES_COMPLETE)) {
                $completeCount += $itemCount;
            }
        }

        return [
            'un_complete' => $unCompleteCount,
            'un_assign' => $unAssignCount,
            'process' => $processCount,
            'complete' => $completeCount
        ];
    }

    public static function rankings()
    {
        $builder = WorkOrder::query()
            ->select(['u.realname', DB::raw("count(*) as count")])
            ->from('work_order as wo')
            ->join('work_order_user as wou', 'wou.work_order_id', '=', 'wo.id')
            ->join('user as u', 'u.id', '=', 'wou.user_id')
            ->where('wo.status', self::STATUS_COMPLETE)
            ->where('wou.is_master', 1)
            ->groupBy('u.realname')
            ->orderByDesc('count')
            ->limit(10);
        $results = [];
        foreach ($builder->get()->toArray() as $item) {
            $results[] = [
                'realname' => $item['realname'],
                'count' => $item['count']
            ];
        }
        return $results;
    }

    /**
     * 审核工单: 区域负责人能审核该区域以及所有子级区域的工单
     *
     * @param $id
     * @throws \Exception
     */
    public static function audit($id)
    {
        $workOrder = WorkOrder::find($id);

        if (empty($workOrder)) {
            throw new \Exception('工单不存在');
        }

        // 审核历史
        $auditHistory = $workOrder->audit->groupBy('target')->toArray();
        $currentUserId = registry('user_id');
        $isCharge = is_charge($currentUserId);
        $isManager = is_manager($currentUserId);
        $target = '';

        $chargeHistory = $auditHistory['charge'] ?? [];
        $managerHistory = $auditHistory['manager'] ?? [];

        // 超级管理员
        if ($currentUserId == 1) {
            if (empty($chargeHistory)) {
                $target = 'charge';
            } elseif (empty($managerHistory)) {
                $target = 'manager';
            } else {
                throw new \Exception('该工单已二次审核, 无需再次审核');
            }
        } else {
            if ($isCharge && $chargeHistory) {
                throw new \Exception('区域主管已审核过该工单, 无需再次审核');
            }
            if ($isManager && $managerHistory) {
                throw new \Exception('区域经理已审核过该工单, 无需再次审核');
            }

            if ($isCharge) {
                $target = 'charge';
            } elseif ($isManager) {
                $target = 'manager';
            }
        }

        $deptService = new DeptService();

        $provinceId = $workOrder->province_id;
        $provinceName = $workOrder->province_name;
        $leaderIds = $deptService->getLeaderIdsByProvinceId($provinceId);
        $managerIds = $deptService->getManagerIdsByProvinceId($provinceId);
        if ($currentUserId != 1 && !in_array($currentUserId, $leaderIds) && !in_array($currentUserId, $managerIds)) {
            throw new \Exception("你不是 {$provinceName} 所在区域的区域主管或区域经理，不能审核工单");
        }

        $content = trim(request()->get('content') ?? '');

        $auditContent = [
            'work_order_id' => $workOrder->id,
            'target' => $target,
            'content' => $content,
            'create_user_id' => $currentUserId
        ];
        $audit = new WorkOrder\Audit($auditContent);
        $audit->saveOrFail();

        // 审核不关闭工单
        // $workOrder->status = self::STATUS_CLOSED;
        // $workOrder->saveOrFail();
    }

    public static function callback_show($id)
    {
        $callbacks = WorkOrder\Callback::query()
            ->select(['work_order_callback.*', 'user.realname'])
            ->join('user', 'user.id', '=', 'work_order_callback.create_user_id')
            ->where('work_order_id', $id)
            ->orderByDesc('id')
            ->get();
        $result = [];
        if (empty($callbacks)) {
            return [];
        }
        foreach ($callbacks as $callback) {
            $target = $callback->target ?? '';
            $position = '';
            if ($target == 'charge') {
                $position = '区域主管';
            }
            if ($target == 'manager') {
                $position = '区域经理';
            }
            $result[] = [
                'auditor' => $callback->realname,
                // 'target' => $callback->target,
                'position' => $position,
                'content' => $callback->content,
                'time' => Carbon::parse($callback->create_time)->toDateTimeString()
            ];
        }
        return $result;
    }

    public static function audit_show($id)
    {
        $audits = WorkOrder\Audit::query()
            ->select(['work_order_audit.*', 'user.realname'])
            ->join('user', 'user.id', '=', 'work_order_audit.create_user_id')
            ->where('work_order_id', $id)
            ->orderByDesc('id')
            ->get();

        $result = [];
        if (empty($audits)) {
            return [];
        }
        foreach ($audits as $audit) {
            $target = $audit->target;
            $position = '';
            if ($target == 'charge') {
                $position = '区域主管';
            }
            if ($target == 'manager') {
                $position = '区域经理';
            }
            $result[] = [
                'auditor' => $audit->realname,
                'target' => $target,
                'position' => $position,
                'content' => $audit->content,
                'time' => Carbon::parse($audit->create_time)->toDateTimeString()
            ];
        }
        return $result;
    }

    // 工单回访
    public static function callback($id)
    {
        $workOrder = WorkOrder::find($id);
        if (empty($workOrder)) {
            throw new \Exception('工单不存在');
        }
        if ($workOrder->status == self::STATUS_CLOSED) {
            throw new \Exception('已关闭的工单不能回访');
        }
        $auditHistory = Audit::query()
            ->where('work_order_id', $id)
            ->get()
            ->groupBy('target')
            ->toArray();
        $managerHistory = $auditHistory['manager'] ?? [];
        if (empty($managerHistory)) {
            throw new \Exception('二级审核未完成， 暂时不能回访该工单');
        }
        $provinceId = $workOrder->province_id;
        $service = new DeptService();
        $leaderIds = $service->getLeaderIdsByProvinceId($provinceId);
        $managerIds = $service->getManagerIdsByProvinceId($provinceId);

        $currentUserId = registry('user_id') ?? 0;
        if (in_array($currentUserId, $leaderIds)) {
            $target = self::CALLBACK_CHARGE;
        } elseif (in_array($currentUserId, $managerIds)) {
            $target = self::CALLBACK_MANAGER;
        } else {
            $target = '';
        }

        $solved = intval(request()->get('solved') ?? 0);
        if ($solved) {
            $solved = 1;
        }

        $contentCheck = trim(request()->get('content_check') ?? '');
        if (!in_array($contentCheck, self::CALLBACK_CONTENT_CHECK)) {
            // throw new \Exception('内容核对情况错误');
        }

        $satisfaction = trim(request()->get('satisfaction') ?? '');
        if (!in_array($satisfaction, self::CALLBACK_SATISFACTION)) {
            // throw new \Exception('满意度错误');
        }

        $content = trim(request()->get('content') ?? '');
        if (!$content) {
            // throw new \Exception('请输入回访内容');
        }

        $callbackData = [
            'work_order_id' => $workOrder->id,
            'content' => $content,
            // 'solved' => '', // $solved,
            'target' => $target,
            // 'content_check' => '', // $contentCheck,
            // 'satisfaction' => '', // $satisfaction
            'create_user_id' => $currentUserId
        ];

        $callback = new WorkOrder\Callback();
        $callback->fill($callbackData);
        $callback->saveOrFail();

        $workOrder->status = self::STATUS_CLOSED;
        $workOrder->saveOrFail();
    }

    private static function receiptData()
    {
        $receiptData = request()->getContent();
        $receiptArr = json_decode($receiptData, true);

        $params = self::params();
        $receiptReason = $params['receipt_reason'];
        $receiptType = $params['receipt_type'];

        $reason = trim($receiptArr['reason'] ?? '');
        $processType = trim($receiptArr['process_type'] ?? '');

        if (!$reason) {
            throw new \Exception('请录入故障原因');
        }

        if (!$processType) {
            throw new \Exception('请录入处理方式');
        }
        $description = $receiptArr['description'] ?? '';

        $images = [];
        foreach ($receiptArr['images'] as $image) {
            $images[] = $image;
        }
        $boms = [];
        foreach ($receiptArr['boms'] ?? [] as $bom) {
            $boms[] = [
                'bom_id' => intval($bom['bom_id']),
                'count' => intval($bom['count'])
            ];
        }

        $services = [];
        foreach ($receiptArr['services'] as $service) {
            $services[] = [
                'name' => trim($service['name'] ?? ''),
                'unit' => trim($service['unit'] ?? ''),
                'count' => intval($service['count'] ?? ''),
                'fee' => floatval($service['fee'] ?? '')
            ];
        }
        // 默认好评
        $satisfaction = intval($receiptArr['satisfaction'] ?? 5);
        $attitude = intval($receiptArr['attitude'] ?? 0);
        $efficiency = intval($receiptArr['efficiency'] ?? 0);

        return [
            'main' => [
                'reason' => $reason,
                'process_type' => $processType,
                'description' => $description,
                'signature' => trim($receiptArr['signature'] ?? ''),
                'satisfaction' => $satisfaction,
                'attitude' => $attitude,
                'efficiency' => $efficiency,
            ],
            'images' => $images,
            'boms' => $boms,
            'services' => $services
        ];
    }

    private static function requestData($id)
    {
        $customerData = request()->getContent();
        $customerArr = json_decode($customerData, true);

        $eventId = intval($customerArr['event_id'] ?? 0);
        if ($eventId) {
            $event = Event::find($eventId);
            if (empty($event)) {
                throw new \Exception('事件不存在');
            }

            if ($event->status != EventRepo::STATUS_UN_DISTRIBUTE) {
                throw new \Exception('仅待分配事件可以转工单');
            }
        }

        $number = self::generateNumber($id);
        $customerId = intval($customerArr['customer_id'] ?? 0);
        $serviceType = trim($customerArr['service_type'] ?? '');
        $serviceContent = trim($customerArr['service_content'] ?? '');
        $planAt = trim($customerArr['plan_at'] ?? '');
        $description = trim($customerArr['description'] ?? '');
        $level = trim($customerArr['level'] ?? '');
        $contactName = trim($customerArr['contact_name'] ?? '');
        $contactPhone = trim($customerArr['contact_phone'] ?? '');
        $countryId = 0;
        $countryCode = '';
        $provinceId = intval($customerArr['province_id'] ?? 0);
        $provinceCode = '';
        $cityId = intval($customerArr['city_id'] ?? 0);
        $cityCode = '';
        $countyId = intval($customerArr['county_id'] ?? 0);
        $countyCode = '';
        $address = trim($customerArr['address'] ?? '');
        $productIds = $customerArr['product_ids'] ?? [];
        $images = $customerArr['images'] ?? [];
        $userId = intval($customerArr['user_id'] ?? '');

        $woType = $customerArr['type'] ?? '';
        $params = self::params();
        $woTypes = $params['wo_types'];
        if (!in_array($woType, $woTypes)) {
            throw new \Exception('工单类型错误');
        }

        $customer = Customer::find($customerId);
        if (empty($customer)) {
            throw new \Exception('请选择工单所属客户');
        }

        $params = self::params();

        $types = $params['types'];
        if (!in_array($serviceType, $types)) {
            throw new \Exception('服务类型错误');
        }

        $contents = $params['contents'];
        if (!in_array($serviceContent, $contents)) {
            throw new \Exception('服务内容错误');
        }
        try {
            $planAt = Carbon::parse($planAt)->toDateTimeString();
        } catch (\Exception $e) {
            throw new \Exception('计划时间错误');
        }
        $levels = $params['levels'];
        if (!in_array($level, $levels)) {
            $level = self::LEVEL_NORMAL;
        }

        if (!$contactName) {
            throw new \Exception('请输入联系人姓名');
        }
        if (!$contactPhone) {
            throw new \Exception('请输入联系人电话');
        }

        $countryName = '';
        $province = CityModel::find($provinceId);
        if (empty($province)) {
            $province = CityModel::byCityCode($provinceId)->first();
        }
        if (empty($province)) {
            throw new \Exception('所属区域省份信息错误');
        }

        $provinceId = $province->id;
        $provinceCode = $province->citycode;
        $provinceName = $province->name;

        $city = CityModel::find($cityId);
        if (empty($city)) {
            $city = CityModel::byCityCode($cityId)->first();
        }
        if (empty($city)) {
            throw new \Exception('所属区域城市信息错误');
        }
        $cityId = $city->id;
        $cityCode = $city->citycode;
        if ($city->pid != $province->id) {
            // throw new \Exception('所属区域城市不是当前所选省份所属');
        }
        $cityName = $city->name;

        $county = CityModel::find($countyId);
        if (empty($county)) {
            $county = CityModel::byCityCode($countyId)->first();
        }
        if (empty($county)) {
            throw new \Exception('所属区域区县信息错误');
        }
        if ($county->pid != $city->id) {
            // throw new \Exception('所属区域区县不是当前所选城市所属');
        }
        $countyId = $county->id;
        $countyCode = $county->citycode;
        $countyName = $county->name;

        if (!trim($address)) {
            throw new \Exception('请输入详细地址');
        }
        $dbProductIds = [];
        if (!is_array($productIds)) {
            throw new \Exception('请添加设备');
        }
        foreach ($productIds as $productId) {
            $itemProductId = intval($productId);
            if (!$itemProductId) {
                continue;
            }
            $itemProduct = Product::find($itemProductId);
            if (empty($itemProduct)) {
                continue;
            }
            if ($itemProduct->customer_id != $customerId) {
                // continue;
            }
            $dbProductIds[] = $itemProductId;
        }
        if (!$dbProductIds) {
            throw new \Exception('请添加设备（已过滤非当前所选客户产品）');
        }
        $user = UserModel::find($userId);
        if (empty($user)) {
            $userId = 0;
        }
        $userIds = [];

        $masterUserName = '';
        $masterUserId = 0;
        if ($user) {
            $userIds[] = [
                'user_id' => $userId,
                'name' => $user->username ?? '',
                'is_master' => 1
            ];
            $masterUserName = $user->realname ?? '';
            $masterUserId = $userId;
        }
        $assistUserIds = request()->get('assist_user_ids') ?? [];
        foreach ($assistUserIds as $assistUserId) {
            $assistUser = UserModel::find($assistUserId);
            if (empty($assistUser)) {
                continue;
            }
            $userIds[] = [
                'user_id' => $assistUserId,
                'name' => $assistUser->username ?? '',
                'is_master' => 0
            ];
        }
        if ($userIds) {
            $hasMaster = 0;
            foreach ($userIds as $userId) {
                if ($userId['is_master']) {
                    $hasMaster = 1;
                    break;
                }
            }
            if (!$hasMaster) {
                throw new Exception('没有选择工单负责人');
            }
        }

        $mainData = [
            'type' => $woType,
            'wo_number' => $number,
            'customer_id' => $customerId,
            'service_type' => $serviceType,
            'service_content' => $serviceContent,
            'plan_at' => $planAt,
            'description' => $description,
            'level' => $level,
            'contact_name' => $contactName,
            'contact_phone' => $contactPhone,
            'country_id' => $countryId,
            'country_code' => $countryCode,
            'province_id' => $provinceId,
            'province_code' => $provinceCode,
            'city_id' => $cityId,
            'city_code' => $cityCode,
            'county_id' => $countyId,
            'county_code' => $countyCode,
            'country_name' => $countryName,
            'province_name' => $provinceName,
            'city_name' => $cityName,
            'county_name' => $countyName,
            'address' => $address,
            'create_user_id' => registry('user_id'),
            'master_username' => $masterUserName,
            'master_user_id' => $masterUserId,
            'event_id' => $eventId
        ];
        if ($userIds) {
            $status = self::STATUS_ASSIGNED;
        } else {
            $status = self::STATUS_CREATED;
        }
        $mainData['status'] = $status;
        $statusHistory = [
            'status' => $status
        ];
        $result = [
            'main' => $mainData,
            'history' => $statusHistory,
            'images' => $images,
            'products' => $dbProductIds,
            'users' => $userIds
        ];
        return $result;
    }

    private static function generateNumber($id)
    {
        $workOrder = WorkOrder::find($id);
        if (!empty($workOrder)) {
            return $workOrder->wo_number;
        }
        $number = 'WO' . date('Ymd') . rand(10000, 99999);
        $numberWorkOrder = WorkOrder::byNumber($number)->first();
        if (!empty($numberWorkOrder)) {
            return self::generateNumber($id);
        }
        return $number;
    }

    private static function wsSend($id, $message)
    {
        try {
            $wsType = WsService::TYPE_WORK_ORDER;
            (new WsService())->sendMessage($id, $wsType, $message);
        } catch (\Exception $e) {
            $log = new Logger('ws_service');
            $log->error($e->getMessage());
        }
    }
}